#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <ctype.h>

#include "../utils/log.h"
#include "../utils/msocket.h"
#include "../utils/mpool.h"
#include "protocol/simple_protocol.h"

#define BACKLOG 32
#define ARRAY_LEN(p) (sizeof(p) / sizeof(p[0]))
#define CLI_MAX 1024
#define LOG_FILE_NAME "service.log"
#define LOG_TAG "log"
static FILE *log_fp = NULL;

/// @brief 初始化tcp套接字
/// @param ip 网卡ip NULL=>监听所有
/// @param port 监听端口
/// @return
socket_t tcp_socket_init(const char *ip, int port)
{
    int step = 0;
    assert(socket_init() == 0);
    socket_t sock = socket_tcp();
    if (socket_invalid == sock) // 创建socket失败
    {
        // perror("socket_tcp() fail");
        log_f_error("socket_tcp() fail:%s", strerror(socket_geterror()));
        step = -4;
        goto fail0;
    }

    // 设置套接字端口可重用，修复了当Socket服务器重启时“地址已在使用(Address already in use)”的错误
    socket_setopt_bool(sock, SO_REUSEADDR, 1);

    struct sockaddr_in addr;
    memset(&addr, 0, sizeof(struct sockaddr_in));
    addr.sin_family = AF_INET;
    addr.sin_port = htons(port);
    if (ip == NULL)
        addr.sin_addr.s_addr = INADDR_ANY;
    else
    {
        /* 将点分十进制的ip地址转为32位整型传入结构体 */
        int s = inet_pton(AF_INET, ip, &addr.sin_addr);
        if (s <= 0)
        {
            if (s == 0)
                log_f_error(LOG_TAG, "inet_pton() format error");
            else
                log_f_error(LOG_TAG, "inet_pton():%s", strerror(socket_geterror()));

            step = -3;
            goto fail0;
        }
    }
    if (socket_bind(sock, (struct sockaddr *)&addr, sizeof(addr)) == socket_error)
    {
        // perror("socket_bind()");
        log_f_error(LOG_TAG, "socket_bind():%s", strerror(socket_geterror()));
        step = -2;
        goto fail0;
    }
    if (socket_listen(sock, BACKLOG) == socket_error)
    {
        // perror("socket_listen()");
        log_f_error(LOG_TAG, "socket_listen():%s", strerror(socket_geterror()));
        step = -1;
        goto fail0;
    }

fail0:
    if (step < 0 && step != -4)
        socket_close(sock);
    else
        step = sock;
    return step;
}

int main(int argc, char *argv[])
{
    log_fp = fopen(LOG_FILE_NAME, "a+");
    if (log_fp == NULL)
        log_fp = fopen(LOG_FILE_NAME, "w");
    log_add_fp(log_fp, LOG_TAG);

    // global_mpool_init(8192);
    socket_t sock = tcp_socket_init(NULL, 8000);
    log_f_info(LOG_TAG, "server sock(%d) init finish", sock);
    assert(sock > 0);

    fd_set rdset;
    socket_t fds[CLI_MAX];
    for (int i = 0; i < ARRAY_LEN(fds); i++)
    {
        if (i == 0)
            fds[i] = sock;
        else
            fds[i] = socket_invalid;
    }
    log_f_info(LOG_TAG, "server sock(%d) sys init finish", sock);

    int max_fd = 0;
    simple_protocol_t buf = {0};
    while (1)
    {
        // 初始化
        FD_ZERO(&rdset);
        max_fd = 0;
        for (int i = 0; i < ARRAY_LEN(fds); i++)
        {
            if (fds[i] == socket_invalid)
                continue;
            FD_SET(fds[i], &rdset);
            if (fds[i] > max_fd)
                max_fd = fds[i];
        }

        // select start
        int select_ret = socket_select(max_fd + 1, &rdset, NULL, NULL, NULL);
        if (select_ret == 0)
        {
            log_f_info(LOG_TAG, "socket_select() time out");
            continue;
        }
        else if (select_ret < 0)
        {
            // perror("socket_select()");
            log_f_error(LOG_TAG, "socket_select() :%s", strerror(socket_geterror()));
            goto fail;
        }

        // 有消息来了
        if (FD_ISSET(fds[0], &rdset)) // 服务器收到的信息
        {
            socket_t cli = socket_accept(sock, NULL, NULL);
            if (cli == socket_error)
            {
                // perror("new cli socket_accept()");
                log_f_error(LOG_TAG, "new cli socket_accept() :%s", strerror(socket_geterror()));
            }
            else
            {
                int idx = -1;
                for (int i = 0; i < ARRAY_LEN(fds); i++)
                {
                    if (fds[i] == socket_invalid)
                    {
                        fds[i] = cli;
                        idx = i;
                        break;
                    }
                }
                // 没有空位了
                if (idx < 0)
                {
                    log_f_warning(LOG_TAG, "new cli(%d) will close, because array is full", cli);
                    socket_close(cli);
                }
                else
                {
                    log_f_info(LOG_TAG, "new cli(%d) accept sucf idx(%d)", cli, idx);
                }
            }
        }
        else // 客户端收到信息
        {
            for (int i = 0; i < ARRAY_LEN(fds); i++)
            {
                // 未站位 或 当前句柄不在监听句柄内 跳过
                if (fds[i] == socket_invalid || !FD_ISSET(fds[i], &rdset))
                    continue;

                memset(&buf, 0, sizeof(buf));
                int ret = socket_recv(fds[i], &buf, sizeof(buf), MSG_WAITALL);
                if (ret <= 0)
                {
                    log_f_info(LOG_TAG, "cli(%d) ret:%d(%s) recv fail ---> will close", fds[i], ret, strerror(socket_geterror()));
                    socket_close(fds[i]);
                    fds[i] = socket_invalid;
                    continue;
                }
                log_debug("server recv cli(%d) bytes(%d) len(%d):%s", fds[i], ret, buf.len, buf.data);

                // 将接收到的数据转小写
                for (int j = 0; j < buf.len; j++)
                {
                    if (buf.data[j] >= 'a' && buf.data[j] <= 'z')
                        buf.data[j] = toupper(buf.data[j]);
                }

                // 回发客户端
                ret = socket_send(fds[i], &buf, ret, MSG_WAITALL);
                if (ret <= 0)
                {
                    log_f_info(LOG_TAG, "cli(%d) ret:%d(%s) send fail ---> will close", fds[i], ret, strerror(socket_geterror()));
                    socket_close(fds[i]);
                    fds[i] = socket_invalid;
                    continue;
                }
            }
        }
    }
fail:
    socket_close(sock);
    assert(socket_cleanup() == 0);
    return 0;
}